package com.google.jessewilson.codesearch.api;

import com.google.jessewilson.codesearch.api.spi.SearchProviderParameters;
import com.google.jessewilson.codesearch.io.jdom.EasyElement;
import com.google.jessewilson.codesearch.io.jdom.Externalizer;
import com.google.jessewilson.codesearch.util.Reflection;
import org.jdom.Element;

import java.io.File;
import java.util.Arrays;

/**
 * A code search query.
 *
 * @author jessewilson
 */
public class Query {

  /** The text to search for. */
  private final String searchTerm;

  /** Returns the file currently open in the editor, if any. */
  private final File editorFile;

  /** Search provider-specific metadata */
  private final SearchProviderParameters searchProviderParameters;

  /**
   * Use {@link com.google.jessewilson.codesearch.api.Query.Builder} to build an instance.
   */
  Query(Builder builder) {
    this.searchTerm = builder.searchTerm;
    this.editorFile = builder.editorFile;
    this.searchProviderParameters = builder.searchProviderParameters;
  }

  public String getSearchTerm() {
    return searchTerm;
  }

  public File getEditorFile() {
    return editorFile;
  }

  public SearchProviderParameters getSearchProviderParameters() {
    return searchProviderParameters;
  }

  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof Query)) {
      return false;
    }
    Query query = (Query) o;
    return (searchTerm.equals(query.searchTerm) &&
        editorFile.equals(query.editorFile) &&
        searchProviderParameters.equals(query.searchProviderParameters));
  }

  public int hashCode() {
    return Arrays.asList(searchTerm, editorFile, searchProviderParameters).hashCode();
  }

  public static class MyExternalizer implements Externalizer<Query> {
    public Query readExternal(Element element) {
      EasyElement easyElement = new EasyElement(element);

      Builder queryBuilder = new Builder();
      queryBuilder.searchTerm(easyElement.getString("searchTerm", ""));
      String editorFileString = easyElement.getString("editorFile", "");
      queryBuilder.editorFile(editorFileString != null ? new File(editorFileString) : null);

      // rip out the query extras, one at a time.
      // For new queries, there should only be one
      for (EasyElement child : easyElement.getChildren()) {
        String className = child.getString("class", null);
        Class<SearchProviderParameters> clas = Reflection.classForName(className, null);
        if (clas == null) {
          continue;
        }

        Externalizer<SearchProviderParameters> externalizer = Finder.find(clas);
        SearchProviderParameters queryExtra =
            externalizer.readExternal(child.getElement().getChild("data"));
        queryBuilder.searchProviderParameters(queryExtra);
      }

      return queryBuilder.build();
    }

    public void writeExternal(Element element, Query query) {
      EasyElement easyElement = new EasyElement(element);
      easyElement.putString("searchTerm", query.searchTerm);
      if (query.editorFile != null) {
        easyElement.putString("editorFile", query.editorFile.toString());
      }

      SearchProviderParameters parameters = query.getSearchProviderParameters();

      // create a child element for the extra data
      if (parameters != null) {
        EasyElement child = new EasyElement("queryExtra");
        element.addContent(child.getElement());
        child.putString("class", parameters.getClass().getName());

        // nest its data in an element of its own
        EasyElement childData = new EasyElement("data");
        child.addChild(childData);
        Externalizer<SearchProviderParameters> externalizer = Finder.find(parameters.getClass());
        externalizer.writeExternal(childData.getElement(), parameters);
      }
    }
  }

  public static class Builder {
    private String searchTerm = "";
    private File editorFile;
    private SearchProviderParameters searchProviderParameters;

    public Builder searchTerm(String searchTerm) {
      this.searchTerm = searchTerm;
      return this;
    }

    public Builder searchProviderParameters(
        SearchProviderParameters searchProviderParameters) {
      this.searchProviderParameters = searchProviderParameters;
      return this;
    }

    public Builder baseQuery(Query baseQuery) {
      searchTerm(baseQuery.getSearchTerm())
          .editorFile(baseQuery.getEditorFile())
          .searchProviderParameters(baseQuery.getSearchProviderParameters());
      return this;
    }

    public Builder editorFile(File editorFile) {
      this.editorFile = editorFile;
      return this;
    }

    public Query build() {
      return new Query(this);
    }
  }
}